/*
 * functionsFAT.c
 *
 *  Created on: 14/09/2011
 *      Author: utn_so
 */

#include "functionsFAT.h"
#include "structsFAT.h"
#include "../../General/Stream/stream.h"
#include "../../General/Logs/log.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <pthread.h>
#include "socketMng.h"
#include "basicFunctions.h"
#include "direccionamiento.h"
#include "cache.h"
#include "rwBlocks.h"

int32_t SIZE_OF_CLUSTER;
int32_t CLUSTERS_IN_FAT;

int32_t BlocksInFAT;
int32_t ClustersInFATPerBlock;
int32_t * bitmapBlocksFAT; // Controla los bloques de la fat que se deben actualizar cuando se llama a UpdateFileAlloctionTable


pthread_mutex_t sem_FileAllocationTable = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t sem_bitmapBlocksFAT = PTHREAD_MUTEX_INITIALIZER;

// "Cuantos Clusters Maximo?" 131072
//CLUSTER FileAllocationTable[131072]; //entonces FileAllocationTable t; t[5] == 6 => el sgte cluster de 5 es 6
CLUSTER * FileAllocationTable;
BootSector BootSectorInformation;
CLUSTER RootDirectory;

/* FILE ALLOCATION TABLE
	0x00000000 Free Cluster— also used as parent directory starting cluster in ".." entries for subdirectories of the root directory (FAT12/16)
	00x00000001 Reserved, do not use
	00x00000002‑0x0FFFFFEF Used cluster; value points to next cluster
	00x0FFFFFF0‑0x0FFFFFF5 Reserved in some contexts[11] or also used[3][9][10]
	00x0FFFFFF6 Reserved; do not use[3][9][10][30]
	0x0FFFFFF7 Bad sector in cluster or reserved cluster
	0x0FFFFFF8‑0x0FFFFFFF Last cluster in file (EOC)
*/

int readBufferFromCluster(CLUSTER cluster, int32_t ReadBytes, STREAM * outStream, const char * pathFile, int32_t aOffset)
{
	int32_t i, x;
	int32_t offset = 0;
	int32_t Result = 0;
	int32_t blocksToRead;
	BLOQUE *Blocklist;
	BLOQUE firstBloque;

	Blocklist = (BLOQUE *)calloc(getSizeOfCluster() / SizeOfBloque, sizeof(BLOQUE));

	firstBloque = BootSectorInformation.reserved_sectors / sectoresPorBloque +
				 (BootSectorInformation.number_of_FATs * (BootSectorInformation.Sectors_per_fileAllocationTable / sectoresPorBloque)) +
				 (cluster - 2) * (BootSectorInformation.sectors_per_cluster / sectoresPorBloque);

	for(i = 0; i < getSizeOfCluster() / SizeOfBloque; i++)
		Blocklist[i] = firstBloque + i;

	blocksToRead = ReadBytes / SizeOfBloque;
	if (ReadBytes % SizeOfBloque > 0)
		blocksToRead++;

	STREAM_Alloc(outStream, blocksToRead * SizeOfBloque * sizeof(unsigned char));
	outStream->length = blocksToRead * SizeOfBloque;

	int32_t from = aOffset / SizeOfBloque;
	if (aOffset % SizeOfBloque > 0) from++;
	for(i = from, x = 0; x < blocksToRead; i++, x++) {
		if (strlen(pathFile) != 0 && (Cache_Read(pathFile, (char *)(outStream->data) + offset, Blocklist[i]) == -1)) { //primero se intenta escribir en la cache
			readBlock(Blocklist[i], SizeOfBloque, (char *)(outStream->data) + offset);
			Cache_Write(pathFile, (char *)(outStream->data) + offset, Blocklist[i]); //escribo en la cache lo que acabo de leer del disco
		}
		else
		if (strlen(pathFile) == 0) {
			readBlock(Blocklist[i], SizeOfBloque, (char *)(outStream->data) + offset);
		}

		offset += SizeOfBloque;
	}

	free(Blocklist);
	return Result;
}

int writeBufferToCluster(CLUSTER cluster, STREAM * buffer, const char * pathFile)
{
	char * newData;
	int32_t offset = 0;
	int32_t Result = 0;
	int32_t i;
	BLOQUE *Blocklist;
	BLOQUE firstBloque;

	Blocklist = (BLOQUE *)calloc(getSizeOfCluster() / SizeOfBloque, sizeof(BLOQUE));

	firstBloque = BootSectorInformation.reserved_sectors / sectoresPorBloque +
				 (BootSectorInformation.number_of_FATs * (BootSectorInformation.Sectors_per_fileAllocationTable / sectoresPorBloque)) +
				 (cluster - 2) * (BootSectorInformation.sectors_per_cluster / sectoresPorBloque);

	//Obtengo los bloques del cluster
	for(i = 0; i < getSizeOfCluster() / SizeOfBloque; i++)
		Blocklist[i] = firstBloque + i;

	newData = (char *)calloc(getSizeOfCluster(), sizeof(char));
	memcpy(newData, buffer->data, buffer->length);

	for(i = 0; i < getSizeOfCluster() / SizeOfBloque; i++) {
		if (Cache_Write(pathFile, newData + offset, Blocklist[i]) == -1) //Intenta primero escribir en la cache
			writeBlock(Blocklist[i], newData + offset);
		offset += SizeOfBloque;
	}

	free(newData);
	return Result;
}

CLUSTER getRootCluster()
{
	return RootDirectory;
}

int EOC(CLUSTER aCluster)
{
	return aCluster >= 0x0FFFFFF8;
}

void setSizeOfCluster(int32_t value)
{
	SIZE_OF_CLUSTER = value;
}

int32_t getSizeOfCluster()
{
	return SIZE_OF_CLUSTER;
}

int32_t getSizeOfSector()
{
	return BootSectorInformation.bytes_per_sector;
}

CLUSTER NextCluster(CLUSTER aCluster)
{
	t_log *LogFile;
	if (aCluster > CLUSTERS_IN_FAT - 1) {
		LogFile = log_create("ProcesoFileSystem", getLogFileName(), getLogLevel(), M_CONSOLE_DISABLE);
		log_error(LogFile, "", "%s","Acceso invalido a la FAT");
		log_destroy(LogFile);
		return EndCluster;
	}
	return FileAllocationTable[aCluster];
}

int setInfoBootSector()
{
	STREAM aStream;
	aStream.data = (unsigned char*)malloc(0x200 * sizeof(unsigned char));

	if (SocketMNG_readSector(0/*lectura del primer sector*/, (char *)(aStream.data)) == -1) {
		perror("Error al leer el BootSector");
		exit(EXIT_FAILURE); //no se debe seguir si no se pudo leer el bootsector
	}

	BootSectorInformation.bytes_per_sector 	  = aStream.data[0x0C] * 0x100 + aStream.data[0x0B];
	BootSectorInformation.sectors_per_cluster = aStream.data[0x0D];
	BootSectorInformation.reserved_sectors    = aStream.data[0x0F] * 0x100 + aStream.data[0x0E];
	BootSectorInformation.number_of_FATs      = aStream.data[0x10];
	BootSectorInformation.Sectors_per_fileAllocationTable = aStream.data[0x27] * 0x1000000 +
															aStream.data[0x26] * 0x10000   +
															aStream.data[0x25] * 0x100     +
															aStream.data[0x24];
	if (aStream.data[0x14] * 0x100 + aStream.data[0x13] != 0)
		BootSectorInformation.sectors = aStream.data[0x14] * 0x100 + aStream.data[0x13];
	else
		BootSectorInformation.sectors = aStream.data[0x23] * 0x1000000 + aStream.data[0x22] * 0x10000 +
										aStream.data[0x21] * 0x100     + aStream.data[0x20];
	RootDirectory = aStream.data[0x2F] * 0x1000000 + aStream.data[0x2E] * 0x10000 +
					aStream.data[0x2D] * 0x100 + aStream.data[0x2C];
	free(aStream.data);
	SIZE_OF_CLUSTER = BootSectorInformation.sectors_per_cluster * BootSectorInformation.bytes_per_sector;
	rwBlocks_SetSizeOfSector(BootSectorInformation.bytes_per_sector);

	return 0;
}

int setFileAllocationTable()
{
	STREAM aStream;
	BLOQUE firstBloque;
	int32_t BytesToRead;
	int32_t i, X;
	uint32_t offset = 0;
	uint32_t bitmapSize;

	BytesToRead = BootSectorInformation.Sectors_per_fileAllocationTable * BootSectorInformation.bytes_per_sector;

	aStream.data = (unsigned char*)malloc(BytesToRead * sizeof(unsigned char));

	printf("BootSectorInformation.Sectors_per_fileAllocationTable = %d\n", BootSectorInformation.Sectors_per_fileAllocationTable);

	firstBloque = BootSectorInformation.reserved_sectors / sectoresPorBloque;
	BlocksInFAT = 1;
	for (i = 0; i < (BootSectorInformation.Sectors_per_fileAllocationTable / sectoresPorBloque) - 1; i++) {
		readBlock(firstBloque + i, SizeOfBloque, (char *)(aStream.data) + offset);
		offset += SizeOfBloque;
		BlocksInFAT++;
	}

	//Lei el disco y tengo el stream en memoria
	X = 0x0;
	FileAllocationTable = (CLUSTER *)malloc(BytesToRead);

	for(i = 0x0; i < BytesToRead; i += sizeof(CLUSTER)) {
		FileAllocationTable[X] = aStream.data[i+3] * 0x1000000 +
								 aStream.data[i+2] * 0x10000   +
								 aStream.data[i+1] * 0x100     +
								 aStream.data[i];
		X++;
	}
	CLUSTERS_IN_FAT = X;
	ClustersInFATPerBlock = CLUSTERS_IN_FAT / BlocksInFAT;

	if (BlocksInFAT % 32 == 0)
		bitmapSize = BlocksInFAT / 32;
	else
		bitmapSize = BlocksInFAT / 32 + 1;

	bitmapBlocksFAT = calloc(bitmapSize, sizeof(int32_t));
	for(i = 0; i < bitmapSize; i++)
		bitmapBlocksFAT[i] = 0;

	free(aStream.data);
	printf("Finalizo lectura de la FAT\n");
	return 0;
}

void freeFileAllocationTable()
{
	if(FileAllocationTable != NULL)
		free(FileAllocationTable);
}

int32_t getSizeFileAllocationTable()
{
	return BootSectorInformation.Sectors_per_fileAllocationTable * BootSectorInformation.bytes_per_sector;
}

void freeBitmapBlockFAT(int32_t aBlock)
{
	int32_t X = aBlock / 32;
	int32_t R = aBlock % 32;
	int32_t aBitMarked = power(2, R);
	pthread_mutex_lock(&sem_bitmapBlocksFAT);
	bitmapBlocksFAT[X] = bitmapBlocksFAT[X] & ~aBitMarked;
	pthread_mutex_unlock(&sem_bitmapBlocksFAT);
}

void setBitmapBlockFAT(CLUSTER aCluster)
{
	int32_t aBlock = aCluster / ClustersInFATPerBlock;
	int32_t X = aBlock / 32;
	int32_t R = aBlock % 32;
	int32_t aBitMarked = power(2, R);
	pthread_mutex_lock(&sem_bitmapBlocksFAT);
	bitmapBlocksFAT[X] = bitmapBlocksFAT[X] | aBitMarked;
	pthread_mutex_unlock(&sem_bitmapBlocksFAT);
}

int isModifiedFATBlock(int32_t aBlock)
{
	int32_t X = aBlock / 32;
	int32_t R = aBlock % 32;
	int32_t aBitMarked = power(2, R);
	int Result;
	pthread_mutex_lock(&sem_bitmapBlocksFAT);
	Result = bitmapBlocksFAT[X] & aBitMarked;
	pthread_mutex_unlock(&sem_bitmapBlocksFAT);
	return Result;
}

int UpdateFileAllocationTable()
{
	//Llamo al proceso disco para escribir en la FAT
	int32_t i;
	BLOQUE firstBloque;
	uint32_t offset = 0;

	firstBloque = BootSectorInformation.reserved_sectors / sectoresPorBloque;
	for (i = 0; i < (BootSectorInformation.Sectors_per_fileAllocationTable / sectoresPorBloque) - 1; i++) {
		if(isModifiedFATBlock(i)) {
			writeBlock(firstBloque + i, (char *)FileAllocationTable + offset);
			freeBitmapBlockFAT(i);
		}
		offset += SizeOfBloque;
	}
	return 0;
}

int UpdateFileAllocationTable_(BLOQUE aBloque) //Si se conoce el bloque que se debe actualizar conviene llamar a esta funcion (mucho mas rapida que la de arriba)
{
	//El parametro aBloque representa el bloque en disco
	BLOQUE firstBloque;
	uint32_t offset;
	firstBloque = BootSectorInformation.reserved_sectors / sectoresPorBloque;
	offset = SizeOfBloque * (aBloque - firstBloque);
	return writeBlock(aBloque, (char *)FileAllocationTable + offset);
}

CLUSTER getCluster(CLUSTER aCluster)
{
	return FileAllocationTable[aCluster];
}

void setCluster(CLUSTER aCluster, CLUSTER newValue)
{
	pthread_mutex_lock(&sem_FileAllocationTable);
	FileAllocationTable[aCluster] = newValue;
	setBitmapBlockFAT(aCluster);
	pthread_mutex_unlock(&sem_FileAllocationTable);
}

void setClusterUnlock(CLUSTER aCluster, CLUSTER newValue)
{
	FileAllocationTable[aCluster] = newValue;
	setBitmapBlockFAT(aCluster);
}

void FIFO_CLUSTER_Create(Fifo_CLUSTER * aClusterList)
{
	aClusterList->Head = NULL;
	aClusterList->Tail = NULL;
}

void FIFO_CLUSTER_Push(Fifo_CLUSTER * aClusterList, CLUSTER aCluster)
{
	Nodo_CLUSTER * aux = (Nodo_CLUSTER *) malloc(sizeof(Nodo_CLUSTER));

	aux->cluster = aCluster;
	aux->nxt = NULL;

	if (aClusterList->Head == NULL) {
		aClusterList->Head = aux;
		aClusterList->Tail = aux;
	}
	else {
		aClusterList->Tail->nxt = aux;
		aClusterList->Tail = aux;
	}
}

void FIFO_CLUSTER_Destroy(Fifo_CLUSTER * aClusterList)
{
	Nodo_CLUSTER * aux;
	while (aClusterList->Head != NULL) {
		aux = aClusterList->Head;
		aClusterList->Head = aClusterList->Head->nxt;
		free(aux);
	}
	aClusterList->Head = NULL;
	aClusterList->Tail = NULL;
}

//Esta funcion sirve para agregar uno o varios clusters libre a una cadena de clusters.
//Si CantFreeClusters es -1 entonces devuelve la lista de todos los clusters
void getListOfFreeClusters(Fifo_CLUSTER * clusterList, int32_t CantFreeClusters)
{
	int32_t cluster;
	int32_t j = 0;
	for(cluster = 0; (cluster < CLUSTERS_IN_FAT) && ((CantFreeClusters == -1) || (j < CantFreeClusters)); cluster++)
	{
		if ( getCluster(cluster) == FreeCluster) {
			FIFO_CLUSTER_Push(clusterList, cluster);
			j++;
		}
	}
}

void getChainClusters(CLUSTER firstCluster, Fifo_CLUSTER * clusterList, uint32_t *cantClusters)
{
	CLUSTER AuxCluster = firstCluster;
	if (AuxCluster != 0) { //Que el archivo tenga un cluster asociado
		while(!EOC(AuxCluster)) {
			FIFO_CLUSTER_Push(clusterList, AuxCluster);
			(*cantClusters)++;
			AuxCluster = NextCluster(AuxCluster);
		}
	}
	else
		FIFO_CLUSTER_Push(clusterList, 0);
}

void getAndSetListOfFreeClusters(Fifo_CLUSTER * clusterList, int32_t CantFreeClusters)
{
	/*obtiene una cantidad de clusters libres y los setea como ocupados*/
	int32_t cluster;
	int32_t j = 0;
	pthread_mutex_lock(&sem_FileAllocationTable);
	for(cluster = 0; (cluster < CLUSTERS_IN_FAT) && ((CantFreeClusters == -1) || (j < CantFreeClusters)); cluster++)
	{
		if ( getCluster(cluster) == FreeCluster) {
			setClusterUnlock(cluster, BusyCluster);
			FIFO_CLUSTER_Push(clusterList, cluster);
			j++;
		}
	}
	pthread_mutex_unlock(&sem_FileAllocationTable);
}

//Esta funcion cuenta la cantidad de clusters libres y de clusters ocupados
void setCantFreeAndBusyClusters(int32_t * CantFreeClusters, int32_t * CantBusyClusters)
{
	int32_t cluster;
	(*CantBusyClusters) = 0;
	(*CantFreeClusters) = 0;
	for(cluster = 0; cluster < CLUSTERS_IN_FAT; cluster++)
	{
		if (getCluster(cluster) == FreeCluster)
			(*CantFreeClusters)++;
		else
			(*CantBusyClusters)++;
	}
}

void destroyListOfClusters(Fifo_CLUSTER * clusterList)
{
	FIFO_CLUSTER_Destroy(clusterList);
}

int FreeChainCluster(CLUSTER firstCluster)
{
	CLUSTER aCluster = firstCluster;
	CLUSTER nextCluster = firstCluster;
	pthread_mutex_lock(&sem_FileAllocationTable);
	while (!EOC(aCluster) && aCluster != FreeCluster) {
		nextCluster = NextCluster(aCluster);
		setClusterUnlock(aCluster, FreeCluster);
		aCluster = nextCluster;
	}
	pthread_mutex_unlock(&sem_FileAllocationTable);
	return 0;
}
